﻿using AspectCore.DynamicProxy;
using Microsoft.Extensions.Caching.Memory;
using Polly;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace Finbook.ConsoleApp.Interceptors
{
    public class HystrixCommandInterceptorAttribute
        : AbstractInterceptorAttribute
    {
        public string FallbackMethod { get; set; }
        public int ExceptionsAllowedBeforeBreaking { get; set; }
        public double IntervalMilliseconds { get; set; }
        public int MaxTimeOutSeconds { get; set; }
        public int MaxRetryTimes { get; set; } = 2;
        public int CacheTTLMilliseconds { get; set; }
        private Policy _policy;
        private static object _lock = new object();
        private IMemoryCache _memoryCache = new MemoryCache(new MemoryCacheOptions());
        public override async Task Invoke(AspectContext context, AspectDelegate next)
        {
            lock (_lock)
            {
                if (_policy == null)
                {
                    _policy = Policy.NoOpAsync();
                }
                if (!string.IsNullOrEmpty(FallbackMethod))
                {
                    _policy = _policy.WrapAsync(Policy.Handle<Exception>().FallbackAsync(async (ctx, token) =>
                    {
                        AspectContext aspectContext = ctx["aspectContext"] as AspectContext;
                        var fallbackMethod = aspectContext.ServiceMethod.DeclaringType.GetMethod(FallbackMethod);
                        var fallbackResult = fallbackMethod.Invoke(aspectContext.Implementation, aspectContext.Parameters);
                        aspectContext.ReturnValue = fallbackResult;
                    }, async (ex, token) => { }));
                }
                if (ExceptionsAllowedBeforeBreaking > 0)
                {
                    _policy = _policy.WrapAsync(Policy.Handle<Exception>().CircuitBreakerAsync(ExceptionsAllowedBeforeBreaking,
                        TimeSpan.FromMilliseconds(IntervalMilliseconds)));
                }
                if (MaxRetryTimes > 0)
                {
                    _policy = _policy.WrapAsync(Policy.Handle<Exception>().WaitAndRetryAsync(MaxRetryTimes, retryCount => TimeSpan.FromMilliseconds(IntervalMilliseconds)));
                }
                if (MaxTimeOutSeconds > 0)
                {
                    _policy = _policy.WrapAsync(Policy.TimeoutAsync(MaxTimeOutSeconds));
                }

            }
            Context pollyContext = new Context();
            pollyContext["aspectContext"] = context;
            if (CacheTTLMilliseconds > 0)
            {
                var cacheKey = $"hystrixCommand_key_{context.ServiceMethod.DeclaringType}.{context.ServiceMethod}{string.Join("_", context.Parameters)}";
                if (_memoryCache.TryGetValue(cacheKey, out var charValue))
                {
                    context.ReturnValue = charValue;
                }
                else
                {
                    await _policy.ExecuteAsync(ctx => next(context), pollyContext);
                    using (var entry = _memoryCache.CreateEntry(cacheKey))
                    {
                        entry.Value = context.ReturnValue;
                        entry.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMilliseconds(CacheTTLMilliseconds);
                    }
                }
            }
            else
            {
                await _policy.ExecuteAsync(ctx => next(context), pollyContext);
            }
        }
    }
}
